今天要來了解兩項概念以及兩項灰常好用的物件
當我們要遊玩一款線上遊戲時,我們會透過我們的裝置(電腦、手機、其他很貴的遊戲機) 連線到各國伺服器裡與其他玩家遊玩,而這項過程就是所謂的與伺服器端連線,你可以想成是我們先告訴一個人說欸我要玩遊戲,然後這個人看一下你有沒有怪怪的(你有沒有開掛、你的裝置是什麼、你的帳號是什麼......),檢查過後才會讓你進來玩,而 "這個人" 就相當於伺服器端。
這就不難理解了吧,我們(玩家)就是客戶端。
在Roblox中,有許多的服務是只限定給客戶端使用的,像是PlayerGui、Backpack、PlayerScripts等,當你在Roblox Studio編輯你的遊戲的時候,你能夠在總管那邊看到的所有服務都是伺服器端的服務,除了你把Players這個服務打開來,裡面會看到你自己的玩家ID,只有這個才會是客戶端,而且是你的客戶端。
而上述例子提到,客戶端與伺服器端是會有溝通的 (上述那個我要玩遊戲的例子),但實際上,客戶端無法直接與伺服器端溝通,更別說更改屬性,伺服器端也無法直接與單一客戶端溝通,只能夠一次與所有客戶端溝通。
既然無法直接溝通,代表我們必須使用一個能夠讓客戶端與伺服器端間接溝通的橋樑,就相當於是有第三個人把你要玩遊戲的這個要求拿去給伺服器端審核的概念。
而這項橋梁就是RemoteEvent和RemoteFunction。
上述提到,我們需要有一個可讓伺服器端與客戶端溝通的橋樑,而雖然我們知道可以用這兩個物件來溝通,但實際上我們也還是沒有辦法確定某客戶端是否能擷取到這項物件(例如你把它們放在伺服器端,這樣只有伺服器端看的到這座橋,但客戶端看不到,因為不能直接擷取),所以通常我們會將這些關於與兩端溝通的物件放在一個叫做ReplicatedStorage的地方,這個服務的作用就是存放可以讓伺服器端與客戶端擷取的物件,因為你不管在哪一端都可以擷取這個服務,自然也可以擷取這個服務裡的物件了。
好的廢話了這麼多該是時候來寫程式了,今天要做的東西不難,就用print來輸出兩端互相傳送的字串。
首先分別在伺服器端與客戶端都建立一個腳本,注意,伺服器端的腳本要用Script,客戶端的腳本要用LocalScript,這樣才可以使用特定函式。
這邊以ServerScriptService作為伺服器端、StarterGui作為客戶端。
接著,在ReplicatedStorage新增一個RemoteEvent
我們先來做客戶端傳送資料給伺服器端
打開LocalScript,先定義好RemoteEvent的位置與要輸出的字串
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
local String = "鐵人賽賽程明天就過半了!"
這邊我們使用了GetService()這項函式來取得遊戲內的服務,這是個比較保險的做法,通常會在定義或直接使用的時候使用這項方法,以避免程式運行過快導致來不及擷取。
接著我們要使用一個叫做燒伺服器的函式,咳咳,我是說叫做FireServer的函式
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
local String = "鐵人賽賽程明天就過半了!"
RemoteEvent:FireServer(String)
這個函式的用意是傳送我們給予的參數給伺服器端,好的那現在傳送了,我們就要在伺服器端接收。
現在,打開在伺服器端的Script,一樣定義好RemoteEvent的位置
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
接著我們要將RemoteEvent被客戶端呼叫的這個事件連接到自訂函式,事件名稱為OnServerEvent
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
RemoteEvent.OnServerEvent:Connect(function(Player, Message)
end)
在這項被連接的自訂函式中,我們會看到有兩項參數被傳入,第一項是傳送要求的玩家,第二項是傳送的要求,還請各位不要忘記打第一個玩家的參數,不然程式會出錯。
接下來就簡單了,我們用昨天教過的結合字串來輸出
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
RemoteEvent.OnServerEvent:Connect(function(Player, Message)
print("玩家"..Player.Name.."傳送了此訊息到伺服器端: "..Message)
end)
注意,在結合字串的時候千萬記得,結合的變數一定要是字串,否則會錯誤,最常見的錯誤就是直接將物件與字串結合,合理的修復方式是將字串與物件的名稱結合 (上述例子就是將字串與傳送要求的玩家的名稱做結合)
現在運行遊戲,沒有出錯的話就可以看到輸出頁面跑出了我們傳送的訊息
接下來,我們來做第二項應用,將伺服器端的資料傳送給特定客戶端
首先,打開伺服器端的Script,將事件連接自訂函式的指令刪掉,接著使用另一個事件來擷取玩家
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
game.Players.PlayerAdded:Connect(function(Player)
RemoteEvent:FireClient(Player)
end)
這項事件會在有玩家加入遊戲後觸發
歐等等,我們忘記定義字串了,但沒有關係,我們也可以直接將字串放入函式作為參數
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
game.Players.PlayerAdded:Connect(function(Player)
RemoteEvent:FireClient(Player, "鐵人賽已經17年啦!?")
end)
接著打開LocalScript,並且將FireServer的函式改成事件,事件名稱為OnClientEvent
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
RemoteEvent.OnClientEvent:Connect(function(Message)
end)
這裡有些不一樣,在這邊我們不需要訂出一個伺服器端的參數,因為伺服器端只有一個,程式不可能不知道。
接下來你會我就不講解了
local RemoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
RemoteEvent.OnClientEvent:Connect(function(Message)
print(Message.." 17年啦!? 明年就18年啦!!?")
end)
運行遊戲,看看輸出是否正確
另外講個小知識,在輸出頁面中,前面的標籤顏色會代表這項訊息是哪裡傳來的,綠色是伺服器端,藍色是客戶端
而RemoteFunction的用法大同小異,比較不同的是,他不只是一座橋梁,他是一個可以來回跑的橋樑
現在,將RemoteEvent改為一項RemoteFunction
到兩個腳本裡把定義稍作修改,並且在伺服器端將FireCliente改成InvokeClient
local RemoteFunction = game:GetService("ReplicatedStorage").RemoteFunction
game.Players.PlayerAdded:Connect(function(Player)
local InvokeClient = RemoteFunction:InvokeClient(Player, "18年啦!!?")
print(InvokeClient)
end)
接著我們到LocalScript去接收
local RemoteFunction = game:GetService("ReplicatedStorage").RemoteFunction
RemoteFunction.OnClientInvoke = function(Message)
print(Message)
return " 再明年就19年啦!?"
end
運行遊戲,檢查輸出是否正確
RemoteFunction的用法跟RemoteEvent很不一樣,而且通常我們不使用RemoteFunction,原因是因為他的流程是一個雙向溝通,代表你必須要一來一往函式才會結束,但萬一你在這一來一往的途中出了錯誤,就會導致伺服器卡住,不僅讓後續的程式無法運行,還會增加後台資源的用量,所以通常除非必要,不然我們都是以RemoteEvent為優先使用。
19年啦!? 再過一年就20年啦!!??